Add folder quota#23
Conversation
|
Important Installation incomplete: to start using Gemini Code Assist, please ask the organization owner(s) to visit the Gemini Code Assist Admin Console and sign the Terms of Services. |
90547f2 to
2b9e8f9
Compare
There was a problem hiding this comment.
Pull request overview
Implements first-level subdirectory (“PVC”) folder quotas for the local FSAL to support Kubernetes PVC size enforcement, including persistence, FSSTAT reporting, enforcement on write/remove/truncate/rename, and CI smoke coverage.
Changes:
- Add quota configuration (including size parsing + bootstrap entries) and wire it through backend creation and startup.
- Introduce
QuotaManager(redb persistence + in-memory cache) and integrate quota-aware enforcement + quota-awarestatvfs()intoLocalFilesystem. - Add quota smoke test script and run it in the GitHub workflow; update NFS error mapping to return
NFS3ERR_DQUOTon quota errors.
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
Cargo.toml |
Adds redb dependency for quota persistence. |
src/config.rs |
Adds QuotaConfig, bootstrap config, and parse_size() helper with tests. |
src/lib.rs |
Exposes config module from the library crate. |
src/fsal/mod.rs |
Adds quota module, FsStats, statvfs() trait method, and quota bootstrap/reconcile hooks. |
src/fsal/quota.rs |
New quota manager with redb-backed storage, cache, and reconciliation helpers + unit tests. |
src/fsal/local/mod.rs |
Wires quota manager into local FSAL; enforces quota on IO ops; implements quota-aware statvfs(), bootstrap, and reconciliation + tests. |
src/nfs/fsstat.rs |
Switches FSSTAT to use backend statvfs() instead of hardcoded values. |
src/nfs/write.rs |
Maps “Quota exceeded” failures to NFS3ERR_DQUOT. |
src/nfs/setattr.rs |
Maps “Quota exceeded” failures to NFS3ERR_DQUOT. |
src/nfs/rename.rs |
Maps “Quota exceeded” failures to NFS3ERR_DQUOT; updates tests for FSAL ctor change. |
src/nfs/remove.rs |
Updates tests for FSAL ctor change. |
src/nfs/mkdir.rs |
Updates tests for FSAL ctor change. |
src/nfs/rmdir.rs |
Updates tests for FSAL ctor change. |
src/nfs/readdirplus.rs |
Updates tests for FSAL ctor change. |
src/main.rs |
Logs quota config; wires quota config into backend; applies bootstrap; schedules reconciliation. |
arcticwolf.example.toml |
Documents quota configuration and bootstrap example. |
tests/test_nfs_quota.sh |
Adds end-to-end quota smoke test using kernel NFS client + df. |
.github/workflows/nfstest-factory.yml |
Enables quota in k8s deployment during CI and runs the new smoke test. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
2b9e8f9 to
87d8304
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
87d8304 to
362312e
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
362312e to
4ae0c10
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| pub async fn apply_bootstrap(&self, bootstrap: &HashMap<String, String>) -> Result<()> { | ||
| for (dir, size_str) in bootstrap { | ||
| if self.get_quota_info(dir).await.is_some() { | ||
| tracing::debug!("Quota bootstrap '{}': already present, skipped", dir); | ||
| continue; | ||
| } | ||
| let limit = crate::config::parse_size(size_str) | ||
| .with_context(|| format!("Invalid bootstrap size for '{}'", dir))?; | ||
| self.set_quota(dir, limit).await?; | ||
| tracing::info!("Quota bootstrap: installed '{}' = {} bytes", dir, limit); | ||
| } |
There was a problem hiding this comment.
apply_bootstrap() accepts arbitrary strings as quota directory keys, but the quota design/docs assume a first-level subdirectory name. Keys containing path separators (e.g. "a/b"), empty components, or ".." won’t match resolve_quota_dir() semantics and can make reconciliation scan unexpected paths under the export. Consider validating bootstrap keys (and/or set_quota) to ensure they are a single path component (no '/' or '\', not '.'/'..') and return a clear error when invalid.
4ae0c10 to
41c51f0
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
41c51f0 to
a8bb10e
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
a8bb10e to
30440c8
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (1)
src/nfs/write.rs:74
e.to_string()is called repeatedly in the error-mapping chain, allocating a new String each time. Storelet msg = e.to_string();once (asrename.rsalready does) and run allcontains(...)checks against that to reduce overhead and keep the mapping logic consistent.
let error_status = if e.to_string().contains("not found")
|| e.to_string().contains("Invalid handle")
{
nfsstat3::NFS3ERR_STALE
} else if e.to_string().contains("Not a file") {
nfsstat3::NFS3ERR_ISDIR
} else if e.to_string().contains("Permission denied") {
nfsstat3::NFS3ERR_ACCES
} else if e.to_string().contains("Quota exceeded")
|| e.to_string().contains("Disk quota exceeded")
{
// Two cases: our own QuotaManager prefixes errors with
// "Quota exceeded ...", and Linux's EDQUOT (which the
// underlying filesystem may return when an OS-level
// project/user quota is in effect) renders as
// "Disk quota exceeded".
nfsstat3::NFS3ERR_DQUOT
} else if e.to_string().contains("No space") {
nfsstat3::NFS3ERR_NOSPC
} else if e.to_string().contains("Read-only") {
nfsstat3::NFS3ERR_ROFS
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
70b17e2 to
e777405
Compare
e777405 to
570d19b
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
570d19b to
f256f13
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
f256f13 to
fa5c167
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
fa5c167 to
be72567
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Adds QuotaConfig (enabled, db_path, bootstrap map) and a parse_size helper that turns strings like "10GB" / "500MB" into byte counts. Pulls in redb for the persistent store landed later. Signed-off-by: Vicente Cheng <vicente.cheng@suse.com>
Introduces QuotaManager: a per-subdirectory byte-quota tracker backed by redb and an in-memory cache, keyed by the first-level subdirectory name under the export root. Cache mutations and the redb commit are held under the same write lock with rollback on persistence failure, so the cache is never observable in a state that was not also persisted. Stored keys are validated at ingress and again on load, so corrupted entries with path separators or `..` cannot slip into the cache where a later scan could escape the export root. check_quota returns "Quota exceeded" errors so NFS handlers can map them to NFS3ERR_DQUOT. Signed-off-by: Vicente Cheng <vicente.cheng@suse.com>
Adds FsStats and Filesystem::statvfs, implemented by LocalFilesystem via libc::statvfs in spawn_blocking. The NFS FSSTAT handler now reports the real underlying filesystem to clients instead of fixed placeholders. Byte multiplier prefers f_frsize, falls back to f_bsize when zero, with saturating_mul to guard against overflow. Signed-off-by: Vicente Cheng <vicente.cheng@suse.com>
Wires QuotaManager into LocalFilesystem. new() takes an Option<&QuotaConfig>; when enabled, a manager is opened against the configured redb database. BackendConfig gains with_quota(). The DB path is rejected if it resolves under the export tree — otherwise NFS clients could tamper with quota state via normal filesystem operations. Containment is checked in absolute terms with a normalize_path helper that safely collapses `..`. statvfs reports quota-aware byte counts for paths inside a quota directory; inode counts and paths outside any quota fall back to libc::statvfs. Also exposes `pub mod config` in lib.rs and plumbs the quota block through main. Signed-off-by: Vicente Cheng <vicente.cheng@suse.com>
Wires QuotaManager into write, remove, setattr_size, and rename.
Accounting is in allocated bytes (st_blocks * 512) rather than
logical length, closing the sparse-file bypass. Writes pre-check
data.len() and adjust by the real delta after the operation;
truncate-down releases space; cross-quota rename walks the source
footprint once and transfers usage after the rename.
Two stat helpers carry the semantics needed:
* allocated_bytes_following — for write/setattr_size, follows
symlinks just like the kernel-level open does.
* refundable_bytes_on_unlink — for remove, refunds only for
regular files with nlink == 1 so symlinks and hard-linked
files cannot be used to drift the counter below reality.
quota_target canonicalises the path so a cross-PVC symlink charges
the PVC where the data actually lives. write and setattr_size
revalidate the resolved path before opening, blocking stale-handle
plus symlink-swap escapes from the export.
Post-FS quota updates are best-effort — a redb error after the
mutation has succeeded is logged, not propagated. NFS handlers for
WRITE/SETATTR/RENAME map both "Quota exceeded" and Linux's "Disk
quota exceeded" to NFS3ERR_DQUOT.
Signed-off-by: Vicente Cheng <vicente.cheng@suse.com>
Adds scan_and_reconcile and reconcile_all on QuotaManager that walk each tracked quota directory, recompute its real byte footprint, and overwrite the cached + persisted usage. Repairs drift caused by out-of-band changes or lost updates. The directory walker is shared with cross-quota rename: the previous duplicate path_size and compute_dir_size collapse into one allocated_path_size in the quota module, using symlink_metadata so the walk cannot escape the subtree. The scan runs without the quota lock; the cache update and redb commit are serialised under the write lock with rollback on failure, preventing a concurrent add_usage between the scan and the persist from being silently overwritten. LocalFilesystem::start_quota_reconciliation is exposed through the Filesystem trait (default no-op) so main can fire-and-forget the scan on startup. Missing directories reconcile to zero, and per-directory failures are logged without aborting the whole pass. Signed-off-by: Vicente Cheng <vicente.cheng@suse.com>
Rounds out folder quota with declarative bootstrap and an end-to-end smoke test driven by a real kernel NFS client. QuotaConfig gains a [quota.bootstrap] TOML block. apply_bootstrap installs each entry only when no quota record exists, so editing the block does not clobber tracked usage. Keys are validated via the shared validate_quota_dir helper (rejecting empty, ".", "..", path separators, NUL bytes). main applies the bootstrap before accepting traffic, gated on quota.enabled. The QuotaManager module's umbrella `#![allow(dead_code)]` is removed now that every method has a runtime caller; remove_quota keeps a per-item allow because it is only exercised by tests but kept for a future admin path. tests/test_nfs_quota.sh exercises under-limit write, over-limit EDQUOT, remove-then-write, truncate-then-write, and `df` reporting. The CI workflow patches the deployment ConfigMap to enable quota and bootstrap a 1MB limit on pvc-quota-test (with the redb file kept outside the export so it cannot be tampered with over NFS), then runs both the smoke test and nfstest_posix. arcticwolf.example.toml documents the new [quota] section. Signed-off-by: Vicente Cheng <vicente.cheng@suse.com>
be72567 to
21577d1
Compare
related: #22